package sk.stuba.fiit.sj.nagy;

import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import sk.stuba.fiit.sj.nagy.Alphabet.Charakter;
import sk.stuba.fiit.sj.nagy.Alphabet.Epsilon;
import sk.stuba.fiit.sj.nagy.Alphabet.NonTerminal;
import sk.stuba.fiit.sj.nagy.Alphabet.Terminal;

public class LLParser {
	
	private static List<Rule> rules;
	
	private static Map<String, Map<Character, Integer>> table;
	
	
	private static void readTable(){
		try {
			table = new HashMap<>();
			BufferedReader br = new BufferedReader(new InputStreamReader(
					LLParser.class.getResourceAsStream("res/table.txt")));
			String line = null;
			String[] terminals = br.readLine().split("#");
			terminals[terminals.length-1] = "\r";
			terminals[terminals.length-1] = "\t";
			terminals[terminals.length-3] = "\n";
			while ((line = br.readLine()) != null){
				String[] vals = line.split("#");
				String nonterminal = vals[0];
				HashMap<Character, Integer> map = new HashMap<Character, Integer>();
				for (int i = 0; i < terminals.length; i++){
					int ruleNum = Integer.parseInt(vals[i+1]);
					if (ruleNum != -1)
						map.put(terminals[i].charAt(0), ruleNum);
				}
				table.put(nonterminal, map);
			}
			br.close();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	
	public static void main(String[] args){
		if (args.length != 2) {
			System.out.println("Usage: java -jar LLParser.jar <command> <arg>");
			System.out.println("Commands:");
			System.out.println("\tfile\tread input from file <arg>");
			System.out.println("\ttext\ttake <arg> as input (should be quoted)");
			return;
		}
		String input = null;
		if (args[0].equals("file")){
			try {
				BufferedReader bis = new BufferedReader(new FileReader(args[1]));
				String line = null;
				StringBuilder text = new StringBuilder();
				while ((line = bis.readLine()) != null){
					text.append(line);
				}
				bis.close();
				input = text.toString();
			} catch (FileNotFoundException e) {
				System.out.println("File '" + args[1] + "' not found.");
				return;
			} catch (IOException e) {
				System.out.println("Error reading file '" + args[1] + "'.");
				return;
			}
		}
		if (args[0].equals("text")) {
			input = args[1];
		}
		input += "$";
		createRules();
		readTable();
		
		List<Charakter> stack = new ArrayList<Alphabet.Charakter>();
		stack.add(new NonTerminal(Consts.XMLDOCUMENT));
		String error = "";
		
		while (!stack.isEmpty()){
			System.out.println("Input: |" + escape(input) + "|, stack: " + printStack(stack));
			Charakter top = stack.remove(0);
			if (top instanceof Terminal){
				if (input.charAt(0) == ((Terminal)top).getValue())
					input = input.substring(1);
				else {
					error = "Error: got '" + escape(input.charAt(0)) + "', expected '" + escape(((Terminal)top).getValue()) + "'";
					break;
				}
			}
			if (top instanceof NonTerminal){
				Map<Character, Integer> row = table.get(((NonTerminal)top).getValue());
				Integer rule = row.get(input.charAt(0));
				if (rule == null) {
					error = "Error: got '" + escape(input.charAt(0)) + "', expected one of " +  escape(row.keySet().toString());
					break;
				}
				Rule newRule = rules.get(rule);
				for (int i = 0; i < newRule.getValues().length; i++)
					stack.add(i, newRule.getValues()[i]);	
			}
		}
		if (!stack.isEmpty())
			error += " Remaining stack: " + printStack(stack); 
		if (input.equals("$") && stack.isEmpty()){
			System.out.println("accepted :)");
		}
		else
			System.out.println("Not accepted :(\n" + error );
		
	}
	
	private static String escape(Character charAt) {
		return escape(charAt.toString());
	}


	private static String escape(String input) {
		return input.toString().replaceAll("\t", "\\\\t").replaceAll("\n", "\\\\n").replace("\r", "\\r");
	}


	private static String printStack(List<Charakter> stack) {
		String result = "";
		for (Charakter c : stack){
			if (c instanceof Terminal)
				result += " '" + ((Terminal)c).getValue() + "'";
			if (c instanceof NonTerminal)
				result += " " + ((NonTerminal)c).getValue();
			if (c instanceof Epsilon)
				result += " e";
		}
		return escape(result);
	}


	private static void createRules(){
		rules = new ArrayList<>();
		rules.add(new Rule(new NonTerminal(Consts.XMLDOCUMENT), 
			new NonTerminal(Consts.SPACEOPT), 
			new Terminal('<'), 
			new NonTerminal(Consts.XMLDOCUMENTT), 
			new NonTerminal(Consts.SPACEOPT)));
		rules.add(new Rule(new NonTerminal(Consts.XMLDOCUMENTT),	
			new NonTerminal(Consts.XMLDECLT), 
			new NonTerminal(Consts.SPACEOPT), 
			new Terminal('<'), 
			new NonTerminal(Consts.ELEMENTT)));
		rules.add(new Rule(new NonTerminal(Consts.XMLDOCUMENTT), 
			new NonTerminal(Consts.ELEMENTT)));
		rules.add(new Rule(new NonTerminal(Consts.XMLDECLT),
				new Terminal('?'),
				new Terminal('x'),
				new Terminal('m'),
				new Terminal('l'),
				new NonTerminal(Consts.SPACEREQ),
				new Terminal('v'),
				new Terminal('e'),
				new Terminal('r'),
				new Terminal('s'),
				new Terminal('i'),
				new Terminal('o'),
				new Terminal('n'),
				new Terminal('='),
				new NonTerminal(Consts.VERNUMB),
				new NonTerminal(Consts.SPACEREQ),
				new Terminal('?'),
				new Terminal('>')));
		rules.add(new Rule(new NonTerminal(Consts.VERNUMB),
				new NonTerminal(Consts.NUMBER),
				new Terminal('.'), 
				new NonTerminal(Consts.NUMBER)));
		rules.add(new Rule(new NonTerminal(Consts.ELEMENTT),
				new NonTerminal(Consts.NAME),
				new NonTerminal(Consts.ELEMENTTT)));
		rules.add(new Rule(new NonTerminal(Consts.ELEMENTTT),
				new NonTerminal(Consts.EMPTYELEMTAGT)));
		rules.add(new Rule(new NonTerminal(Consts.ELEMENTTT),
				new NonTerminal(Consts.STARTTAGT),
				new NonTerminal(Consts.SPACEOPT), 
				new NonTerminal(Consts.ELEMENTTTT)));
		rules.add(new Rule(new NonTerminal(Consts.ELEMENTTTT),
				new NonTerminal(Consts.WORDS),
				new Terminal('<'), 
				new NonTerminal(Consts.ENDTAG)));
		rules.add(new Rule(new NonTerminal(Consts.ELEMENTTTT),
				new Terminal('<'),
				new NonTerminal(Consts.ELEMENTTTTT)));
		rules.add(new Rule(new NonTerminal(Consts.ELEMENTTTTT),
				new NonTerminal(Consts.ELEMENTT),
				new NonTerminal(Consts.SPACEOPT),
				new Terminal('<'), 
				new NonTerminal(Consts.ENDTAG)));
		rules.add(new Rule(new NonTerminal(Consts.ELEMENTTTTT),
				new NonTerminal(Consts.ENDTAG)));
		rules.add(new Rule(new NonTerminal(Consts.EMPTYELEMTAGT),
				new Terminal('/'),
				new Terminal('>')));
		rules.add(new Rule(new NonTerminal(Consts.STARTTAGT),
				new Terminal('>')));
		rules.add(new Rule(new NonTerminal(Consts.ENDTAG),
				new Terminal('/'),
				new NonTerminal(Consts.NAME),
				new Terminal('>')));
		rules.add(new Rule(new NonTerminal(Consts.WORDS),
				new NonTerminal(Consts.WORD),
				new NonTerminal(Consts.WORDST)));
		rules.add(new Rule(new NonTerminal(Consts.WORDST),
				new NonTerminal(Consts.SPACEREQ),
				new NonTerminal(Consts.WORDSTT)));
		rules.add(new Rule(new NonTerminal(Consts.WORDST),
				new Epsilon()));
		rules.add(new Rule(new NonTerminal(Consts.WORDSTT),
				new NonTerminal(Consts.WORDS)));
		rules.add(new Rule(new NonTerminal(Consts.WORDSTT),
				new Epsilon()));
		rules.add(new Rule(new NonTerminal(Consts.NAME),
				new NonTerminal(Consts.LETTER),
				new NonTerminal(Consts.NAMECHART)));
		rules.add(new Rule(new NonTerminal(Consts.NAME),
				new Terminal('_'),
				new NonTerminal(Consts.NAMECHART)));
		rules.add(new Rule(new NonTerminal(Consts.NAME),
				new Terminal(':'),
				new NonTerminal(Consts.NAMECHART)));
		rules.add(new Rule(new NonTerminal(Consts.NAMECHART),
				new NonTerminal(Consts.NAMECHAR),
				new NonTerminal(Consts.NAMECHART)));
		rules.add(new Rule(new NonTerminal(Consts.NAMECHART),
				new Epsilon()));
		rules.add(new Rule(new NonTerminal(Consts.NAMECHAR),
				new NonTerminal(Consts.LETTER)));
		rules.add(new Rule(new NonTerminal(Consts.NAMECHAR),
				new NonTerminal(Consts.DIGIT)));
		rules.add(new Rule(new NonTerminal(Consts.NAMECHAR),
				new Terminal('.')));
		rules.add(new Rule(new NonTerminal(Consts.NAMECHAR),
				new Terminal('-')));
		rules.add(new Rule(new NonTerminal(Consts.NAMECHAR),
				new Terminal('_')));
		rules.add(new Rule(new NonTerminal(Consts.NAMECHAR),
				new Terminal(':')));
		rules.add(new Rule(new NonTerminal(Consts.LETTER),
				new Terminal('a')));
		rules.add(new Rule(new NonTerminal(Consts.LETTER),
				new Terminal('b')));
		rules.add(new Rule(new NonTerminal(Consts.LETTER),
				new Terminal('c')));
		rules.add(new Rule(new NonTerminal(Consts.LETTER),
				new Terminal('d')));
		rules.add(new Rule(new NonTerminal(Consts.LETTER),
				new Terminal('e')));
		rules.add(new Rule(new NonTerminal(Consts.LETTER),
				new Terminal('f')));
		rules.add(new Rule(new NonTerminal(Consts.LETTER),
				new Terminal('g')));
		rules.add(new Rule(new NonTerminal(Consts.LETTER),
				new Terminal('h')));
		rules.add(new Rule(new NonTerminal(Consts.LETTER),
				new Terminal('i')));
		rules.add(new Rule(new NonTerminal(Consts.LETTER),
				new Terminal('j')));
		rules.add(new Rule(new NonTerminal(Consts.LETTER),
				new Terminal('k')));
		rules.add(new Rule(new NonTerminal(Consts.LETTER),
				new Terminal('l')));
		rules.add(new Rule(new NonTerminal(Consts.LETTER),
				new Terminal('m')));
		rules.add(new Rule(new NonTerminal(Consts.LETTER),
				new Terminal('n')));
		rules.add(new Rule(new NonTerminal(Consts.LETTER),
				new Terminal('o')));
		rules.add(new Rule(new NonTerminal(Consts.LETTER),
				new Terminal('p')));
		rules.add(new Rule(new NonTerminal(Consts.LETTER),
				new Terminal('q')));
		rules.add(new Rule(new NonTerminal(Consts.LETTER),
				new Terminal('r')));
		rules.add(new Rule(new NonTerminal(Consts.LETTER),
				new Terminal('s')));
		rules.add(new Rule(new NonTerminal(Consts.LETTER),
				new Terminal('t')));
		rules.add(new Rule(new NonTerminal(Consts.LETTER),
				new Terminal('u')));
		rules.add(new Rule(new NonTerminal(Consts.LETTER),
				new Terminal('v')));
		rules.add(new Rule(new NonTerminal(Consts.LETTER),
				new Terminal('w')));
		rules.add(new Rule(new NonTerminal(Consts.LETTER),
				new Terminal('x')));
		rules.add(new Rule(new NonTerminal(Consts.LETTER),
				new Terminal('y')));
		rules.add(new Rule(new NonTerminal(Consts.LETTER),
				new Terminal('z')));
		rules.add(new Rule(new NonTerminal(Consts.LETTER),
				new Terminal('A')));
		rules.add(new Rule(new NonTerminal(Consts.LETTER),
				new Terminal('B')));
		rules.add(new Rule(new NonTerminal(Consts.LETTER),
				new Terminal('C')));
		rules.add(new Rule(new NonTerminal(Consts.LETTER),
				new Terminal('D')));
		rules.add(new Rule(new NonTerminal(Consts.LETTER),
				new Terminal('E')));
		rules.add(new Rule(new NonTerminal(Consts.LETTER),
				new Terminal('F')));
		rules.add(new Rule(new NonTerminal(Consts.LETTER),
				new Terminal('G')));
		rules.add(new Rule(new NonTerminal(Consts.LETTER),
				new Terminal('H')));
		rules.add(new Rule(new NonTerminal(Consts.LETTER),
				new Terminal('I')));
		rules.add(new Rule(new NonTerminal(Consts.LETTER),
				new Terminal('J')));
		rules.add(new Rule(new NonTerminal(Consts.LETTER),
				new Terminal('K')));
		rules.add(new Rule(new NonTerminal(Consts.LETTER),
				new Terminal('L')));
		rules.add(new Rule(new NonTerminal(Consts.LETTER),
				new Terminal('M')));
		rules.add(new Rule(new NonTerminal(Consts.LETTER),
				new Terminal('N')));
		rules.add(new Rule(new NonTerminal(Consts.LETTER),
				new Terminal('O')));
		rules.add(new Rule(new NonTerminal(Consts.LETTER),
				new Terminal('P')));
		rules.add(new Rule(new NonTerminal(Consts.LETTER),
				new Terminal('Q')));
		rules.add(new Rule(new NonTerminal(Consts.LETTER),
				new Terminal('R')));
		rules.add(new Rule(new NonTerminal(Consts.LETTER),
				new Terminal('S')));
		rules.add(new Rule(new NonTerminal(Consts.LETTER),
				new Terminal('T')));
		rules.add(new Rule(new NonTerminal(Consts.LETTER),
				new Terminal('U')));
		rules.add(new Rule(new NonTerminal(Consts.LETTER),
				new Terminal('V')));
		rules.add(new Rule(new NonTerminal(Consts.LETTER),
				new Terminal('W')));
		rules.add(new Rule(new NonTerminal(Consts.LETTER),
				new Terminal('X')));
		rules.add(new Rule(new NonTerminal(Consts.LETTER),
				new Terminal('Y')));
		rules.add(new Rule(new NonTerminal(Consts.LETTER),
				new Terminal('Z')));
		rules.add(new Rule(new NonTerminal(Consts.NUMBER),
				new NonTerminal(Consts.DIGIT),
				new NonTerminal(Consts.NUMBERT))); 
		rules.add(new Rule(new NonTerminal(Consts.NUMBERT),
				new NonTerminal(Consts.NUMBER)));
		rules.add(new Rule(new NonTerminal(Consts.NUMBERT),
				new Epsilon()));
		rules.add(new Rule(new NonTerminal(Consts.DIGIT),
				new Terminal('0')));
		rules.add(new Rule(new NonTerminal(Consts.DIGIT),
				new Terminal('1')));
		rules.add(new Rule(new NonTerminal(Consts.DIGIT),
				new Terminal('2')));
		rules.add(new Rule(new NonTerminal(Consts.DIGIT),
				new Terminal('3')));
		rules.add(new Rule(new NonTerminal(Consts.DIGIT),
				new Terminal('4')));
		rules.add(new Rule(new NonTerminal(Consts.DIGIT),
				new Terminal('5')));
		rules.add(new Rule(new NonTerminal(Consts.DIGIT),
				new Terminal('6')));
		rules.add(new Rule(new NonTerminal(Consts.DIGIT),
				new Terminal('7')));
		rules.add(new Rule(new NonTerminal(Consts.DIGIT),
				new Terminal('8')));
		rules.add(new Rule(new NonTerminal(Consts.DIGIT),
				new Terminal('9')));
		rules.add(new Rule(new NonTerminal(Consts.WORD),
				new NonTerminal(Consts.CHAR),
				new NonTerminal(Consts.WORDT)));
		rules.add(new Rule(new NonTerminal(Consts.WORDT),
				new NonTerminal(Consts.WORD)));
		rules.add(new Rule(new NonTerminal(Consts.WORDT),	
				new Epsilon()));
		rules.add(new Rule(new NonTerminal(Consts.CHAR),
				new NonTerminal(Consts.LETTER)));
		rules.add(new Rule(new NonTerminal(Consts.CHAR),
				new NonTerminal(Consts.DIGIT)));
		rules.add(new Rule(new NonTerminal(Consts.CHAR),
				new Terminal('.')));
		rules.add(new Rule(new NonTerminal(Consts.CHAR),
				new Terminal(',')));
		rules.add(new Rule(new NonTerminal(Consts.CHAR),
				new Terminal(':')));
		rules.add(new Rule(new NonTerminal(Consts.CHAR),
				new Terminal(';')));
		rules.add(new Rule(new NonTerminal(Consts.CHAR),
				new Terminal('-')));
		rules.add(new Rule(new NonTerminal(Consts.CHAR),
				new Terminal('_')));
		rules.add(new Rule(new NonTerminal(Consts.SPACE),
				new Terminal(' ')));
		rules.add(new Rule(new NonTerminal(Consts.SPACE),
				new Terminal('\n')));
		rules.add(new Rule(new NonTerminal(Consts.SPACE),
				new Terminal('\t')));
		rules.add(new Rule(new NonTerminal(Consts.SPACEOPT),
				new NonTerminal(Consts.SPACE),
				new NonTerminal(Consts.SPACEOPT)));
		rules.add(new Rule(new NonTerminal(Consts.SPACEOPT),
				new Epsilon()));
		rules.add(new Rule(new NonTerminal(Consts.SPACEREQ),
				new NonTerminal(Consts.SPACE),
				new NonTerminal(Consts.SPACEOPT)));
		rules.add(new Rule(new NonTerminal(Consts.SPACE),
				new Terminal('\r')));
	}
}
